<template>
    <div class="app-container system-menu">
        <sib-table
            row-key="menuId"
            ref="sibTableRef"
            :index-config="indexConfig"
            :search-config="searchConfig"
            :table-column="tableColumn"
            :request-config="requestConfig"
            :show-pagination="false"
        >
            <!-- 自定义插槽，插槽名称为对应列配置的 code -->
            <template #operate="{ row }">
                <el-button type="primary" v-hasPermi="['system:menu:edit']" link @click="handleEdit(row)">修改</el-button>
                <el-button type="primary" v-hasPermi="['system:menu:add']" v-if="row.menuType !== 'F'" link @click="handleAdd(row)">新增子菜单</el-button>
                <el-button type="primary" v-hasPermi="['system:menu:add']" link @click="handleCopy(row)">复制</el-button>
                <!-- eslint-disable-next-line -->
                <el-button type="danger" link v-hasPermi="['system:menu:remove']" :disabled="(row.children || []).length" :title="(row.children || []).length ? '存在子菜单，不可删除' : ''" @click="handleDelete(row)">删除</el-button>
            </template>
            <!-- 自定义表单与列表之间的内容 -->
            <template #operation-container>
                <el-button type="primary" plain icon="Plus" v-hasPermi="['system:menu:add']" @click="handleAdd">新增</el-button>
            </template>
            <template #icon="{ row }">
                <svg-icon :icon-class="row.icon"></svg-icon>
            </template>
        </sib-table>

        <el-dialog
            class="form-dialog"
            v-if="dialogConfig.visible"
            append-to-body
            draggable
            align-center
            v-model="dialogConfig.visible"
            :close-on-click-modal="false"
            :title="dialogConfig.title"
            :width="dialogConfig.width || '500px'"
        >
            <sib-form
                ref="sibFormRef"
                reset-text="取消"
                :row-field-size="dialogConfig.rowFieldSize"
                :auto-operate-btn-width="dialogConfig.autoOperateBtnWidth"
                :fields="dialogConfig.fields"
                :form="dialogConfig.form"
                @item-change="formItemChange"
                @reset="dialogConfig.visible = false"
                @submit="handleSubmit"
            >
                <template #icon="{ form }">
                    <el-popover placement="bottom-start" :visible="iconVisible" trigger="click" :width="350">
                        <template #reference>
                            <el-input class="icon-input" v-model="form.icon" placeholder="点击选择图标" @click="iconVisible = !iconVisible" readonly>
                                <template #prefix>
                                    <svg-icon v-if="form.icon" :icon-class="form.icon" class="el-input__icon"></svg-icon>
                                    <el-icon v-else><search></search></el-icon>
                                </template>
                            </el-input>
                        </template>
                        <div class="menu-icon-list">
                            <div v-for="(name, index) in iconNames" :key="index" @click="handleSelectIcon(name, form)" class="icon-box">
                                <svg-icon :icon-class="name" />
                                <span>{{ name }}</span>
                            </div>
                        </div>
                    </el-popover>
                </template>
            </sib-form>
        </el-dialog>
    </div>
</template>

<script>
import { ref, reactive } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import axios from '@/utils/request';

const icons = import.meta.glob('../../../assets/icons/*.svg');

const iconNames = Object.keys(icons).map(key => key.match(/\/([^/]+)\.svg$/)[1]);

const resetMenuList = (menus, pId = '0') => {
    const parentList = menus.filter(({ parentId }) => String(parentId) === String(pId));
    return parentList.map(menu => ({ ...menu, children: resetMenuList(menus, menu.menuId) }));
};

export default {
    // eslint-disable-next-line
    name: 'Menu',
    setup() {
        const sibTableRef = ref(null);
        // 查询模块表单配置
        const searchConfig = reactive({
            showExpand: false,
            rowFieldSize: 4,
            fields: [
                {
                    label: '菜单名称',
                    code: 'menuName',
                    type: 'text',
                    maxlength: 100,
                },
                {
                    label: '状态',
                    code: 'status',
                    type: 'select',
                    optionProps: { value: 'dictValue', label: 'dictLabel' },
                    requestConfig: {
                        url: '/system/dict/data/type/sys_normal_disable',
                        method: 'get',
                    },
                    options: [],
                },
            ],
        });
        // 表格首列配置
        const indexConfig = reactive({ type: 'hidden' });
        // 表格表头配置
        const tableColumn = ref([
            {
                label: '菜单名称',
                code: 'menuName',
                minWidth: 160,
            },
            {
                label: '菜单类型',
                code: 'menuType',
                minWidth: 100,
                formatter: ({ menuType }) => ({ M: '目录', C: '菜单', F: '按钮' }[menuType]),
            },
            {
                label: '图标',
                code: 'icon',
                type: 'slot',
                minWidth: 100,
            },
            {
                label: '排序',
                code: 'orderNum',
                minWidth: 60,
            },
            {
                label: '权限标识',
                code: 'perms',
                minWidth: 150,
            },
            {
                label: '组件路径',
                code: 'component',
                minWidth: 150,
            },
            {
                label: '状态',
                code: 'status',
                type: 'tag',
                relateFieldCode: 'status',
                tagType: ({ status }) => (String(status) === '0' ? 'success' : 'danger'),
            },
            {
                label: '创建时间',
                code: 'createTime',
                type: 'timeStamp',
                format: 'YYYY-MM-DD hh:mm',
                minWidth: 160,
            },
            {
                label: '操作',
                code: 'operate',
                type: 'slot',
                minWidth: 260,
                fixed: 'right',
            },
        ]);
        const menuData = ref([]);
        // 列表查询接口配置
        const requestConfig = reactive({
            url: '/system/menu/list',
            method: 'get',
            // 自定义传参
            customSearchParams: obj => {
                const params = {};
                if ([0, false].includes(obj.menuName) || obj.menuName) params.menuName = obj.menuName;
                if ([0, false].includes(obj.status) || obj.status) params.status = obj.status;
                return params;
            },
            callback: res => {
                menuData.value = res.data || [];
                return resetMenuList(menuData.value);
            },
        });

        const sibFormRef = ref(null);
        // 新增、编辑等弹窗配置
        const dialogConfig = reactive({
            title: '',
            width: '900px',
            rowFieldSize: 2,
            autoOperateBtnWidth: false,
            // add/edit/copy
            type: 'add',
            visible: false,
            // 表单字段配置-目录
            directoryFields: [
                {
                    label: '上级菜单',
                    code: 'parentId',
                    required: true,

                    formatedValueCode: 'parentName',
                    type: 'tree',
                    optionProps: {
                        label: 'menuName',
                        value: 'menuId',
                    },

                    // type: 'cascader',
                    // options: [],
                    // optionProps: {
                    //     emitPath: false,
                    //     checkStrictly: true,
                    //     label: 'menuName',
                    //     value: 'menuId',
                    // },
                },
                {
                    label: '菜单类型',
                    code: 'menuType',
                    type: 'radio',
                    options: [
                        {
                            label: '目录',
                            value: 'M',
                        },
                        {
                            label: '菜单',
                            value: 'C',
                        },
                        {
                            label: '按钮',
                            value: 'F',
                        },
                    ],
                    required: true,
                    width: '100%',
                },
                {
                    label: '菜单名称',
                    code: 'menuName',
                    type: 'text',
                    maxlength: 50,
                    required: true,
                },
                {
                    label: '菜单图标',
                    code: 'icon',
                    type: 'slot',
                },
                {
                    label: '路由地址',
                    labelTips: '访问的路由地址，如：`user`，如外网地址需内链访问则以`http(s)://`开头',
                    code: 'path',
                    type: 'text',
                    maxlength: 300,
                    required: true,
                },
                {
                    label: '显示排序',
                    code: 'orderNum',
                    type: 'number',
                    controlsPosition: 'right',
                    required: true,
                },
                {
                    label: '菜单状态',
                    labelTips: '选择停用则路由将不会出现在侧边栏，也不能被访问',
                    code: 'status',
                    type: 'radio',
                    optionProps: { value: 'dictValue', label: 'dictLabel' },
                    requestConfig: {
                        url: '/system/dict/data/type/sys_normal_disable',
                        method: 'get',
                    },
                    options: [],
                    required: true,
                },
                {
                    label: '显示状态',
                    labelTips: '选择隐藏则路由将不会出现在侧边栏，但仍然可以访问',
                    code: 'visible',
                    type: 'radio',
                    optionProps: { value: 'dictValue', label: 'dictLabel' },
                    requestConfig: {
                        url: '/system/dict/data/type/sys_show_hide',
                        method: 'get',
                    },
                    options: [],
                    required: true,
                },
                {
                    label: '是否外链',
                    labelTips: '选择是外链则路由地址需要以`http(s)://`开头',
                    code: 'isFrame',
                    type: 'radio',
                    options: [
                        {
                            label: '是',
                            value: '0',
                        },
                        {
                            label: '否',
                            value: '1',
                        },
                    ],
                    required: true,
                },
            ],
            // 表单字段配置-菜单
            menuFields: [
                {
                    label: '上级菜单',
                    code: 'parentId',
                    required: true,

                    formatedValueCode: 'parentName',
                    type: 'tree',
                    optionProps: {
                        label: 'menuName',
                        value: 'menuId',
                    },

                    // type: 'cascader',
                    // options: [],
                    // optionProps: {
                    //     emitPath: false,
                    //     checkStrictly: true,
                    //     label: 'menuName',
                    //     value: 'menuId',
                    // },
                },
                {
                    label: '菜单类型',
                    code: 'menuType',
                    type: 'radio',
                    options: [
                        {
                            label: '目录',
                            value: 'M',
                        },
                        {
                            label: '菜单',
                            value: 'C',
                        },
                        {
                            label: '按钮',
                            value: 'F',
                        },
                    ],
                    required: true,
                    width: '100%',
                },
                {
                    label: '菜单名称',
                    code: 'menuName',
                    type: 'text',
                    maxlength: 50,
                    required: true,
                },
                {
                    label: '菜单图标',
                    code: 'icon',
                    type: 'slot',
                },
                {
                    label: '路由地址',
                    labelTips: '访问的路由地址，如：`user`，如外网地址需内链访问则以`http(s)://`开头',
                    code: 'path',
                    type: 'text',
                    maxlength: 300,
                    required: true,
                },
                {
                    label: '路由参数',
                    code: 'query',
                    type: 'text',
                    maxlength: 255,
                },
                {
                    label: '组件路径',
                    labelTips: '访问的组件路径，如："system/user/index"，默认在 "views" 目录下',
                    code: 'component',
                    type: 'text',
                    maxlength: 300,
                },
                {
                    label: '显示排序',
                    code: 'orderNum',
                    type: 'number',
                    controlsPosition: 'right',
                    required: true,
                },
                {
                    label: '权限字符',
                    labelTips: "控制器中定义的权限字符，如：@PreAuthorize(`@ss.hasPermi('system:user:list')`)",
                    code: 'perms',
                    type: 'text',
                    maxlength: 100,
                },
                {
                    label: '是否缓存',
                    labelTips: '选择是则会被 "keep-alive" 缓存，需要匹配组件的 "name" 和地址保持一致',
                    code: 'isCache',
                    type: 'radio',
                    options: [
                        {
                            label: '缓存',
                            value: '0',
                        },
                        {
                            label: '不缓存',
                            value: '1',
                        },
                    ],
                    required: true,
                },
                {
                    label: '菜单状态',
                    labelTips: '选择停用则路由将不会出现在侧边栏，也不能被访问',
                    code: 'status',
                    type: 'radio',
                    optionProps: { value: 'dictValue', label: 'dictLabel' },
                    requestConfig: {
                        url: '/system/dict/data/type/sys_normal_disable',
                        method: 'get',
                    },
                    options: [],
                    required: true,
                },
                {
                    label: '显示状态',
                    labelTips: '选择隐藏则路由将不会出现在侧边栏，但仍然可以访问',
                    code: 'visible',
                    type: 'radio',
                    optionProps: { value: 'dictValue', label: 'dictLabel' },
                    requestConfig: {
                        url: '/system/dict/data/type/sys_show_hide',
                        method: 'get',
                    },
                    options: [],
                    required: true,
                },
                {
                    label: '是否外链',
                    labelTips: '选择是外链则路由地址需要以`http(s)://`开头',
                    code: 'isFrame',
                    type: 'radio',
                    options: [
                        {
                            label: '是',
                            value: '0',
                        },
                        {
                            label: '否',
                            value: '1',
                        },
                    ],
                    required: true,
                },
            ],
            // 表单字段配置-按钮
            buttonFields: [
                {
                    label: '上级菜单',
                    code: 'parentId',
                    required: true,

                    formatedValueCode: 'parentName',
                    type: 'tree',
                    optionProps: {
                        label: 'menuName',
                        value: 'menuId',
                    },

                    // type: 'cascader',
                    // options: [],
                    // optionProps: {
                    //     emitPath: false,
                    //     checkStrictly: true,
                    //     label: 'menuName',
                    //     value: 'menuId',
                    // },
                },
                {
                    label: '菜单类型',
                    code: 'menuType',
                    type: 'radio',
                    options: [
                        {
                            label: '目录',
                            value: 'M',
                        },
                        {
                            label: '菜单',
                            value: 'C',
                        },
                        {
                            label: '按钮',
                            value: 'F',
                        },
                    ],
                    required: true,
                    width: '100%',
                },
                {
                    label: '菜单名称',
                    code: 'menuName',
                    type: 'text',
                    maxlength: 50,
                    required: true,
                },
                {
                    label: '显示排序',
                    code: 'orderNum',
                    type: 'number',
                    controlsPosition: 'right',
                    required: true,
                },
                {
                    label: '权限字符',
                    labelTips: "控制器中定义的权限字符，如：@PreAuthorize(`@ss.hasPermi('system:user:list')`)",
                    code: 'perms',
                    type: 'text',
                    maxlength: 100,
                },
            ],
            fields: [],
            form: {},
        });
        const iconVisible = ref(false);

        // 动态刷新上级菜单的可选值
        const changeParentSelector = (data = {}) => {
            const filterMethod = ({ menuId, menuType }) => {
                const currentMenuType = data.menuType || 'M';
                // 当前选择的是目录或菜单，那么只允许选择目录
                if (['M', 'C'].includes(currentMenuType)) return menuId !== data.menuId && ['M'].includes(menuType);
                // 当前选择的是按钮，那么允许选择目录或菜单
                if (['F'].includes(currentMenuType)) return menuId !== data.menuId && ['M', 'C'].includes(menuType);
                return menuId !== data.menuId;
            };
            const parentSelector = dialogConfig.fields.find(({ code }) => code === 'parentId');
            parentSelector.options = [
                {
                    menuName: '主类目',
                    menuId: 0,
                    children: resetMenuList(menuData.value.filter(filterMethod)),
                },
            ];
        };
        // 切换菜单类型触发表单项更新
        const changeMenuTypeFields = data => {
            const type = (data || {}).menuType || 'M';
            const fieldConf = {
                M: {
                    name: 'directoryFields',
                    defaultForm: {
                        menuType: 'M',
                        isFrame: '1',
                        visible: '0',
                        status: '0',
                    },
                },
                C: {
                    name: 'menuFields',
                    defaultForm: {
                        menuType: 'C',
                        isFrame: '1',
                        visible: '0',
                        status: '0',
                        isCache: '0',
                    },
                },
                F: {
                    name: 'buttonFields',
                    defaultForm: {},
                },
            };
            const { name, defaultForm } = fieldConf[type];
            // prettier-ignore
            const parentMenu = (Object.prototype.toString.call(data.parentId) === '[object Array]' ? data.parentId[0] : menuData.value.find(({ menuId }) => menuId === data.parentId)) || {}
            data.parentName = parentMenu.menuName || '主类目';
            data.parentId = [parentMenu.menuId || 0];
            dialogConfig.fields = window.SIB.deepCopy(dialogConfig[name]);
            dialogConfig.form = { ...defaultForm, ...dialogConfig.form, ...(data || {}) };
            changeParentSelector(data);
        };

        return {
            sibTableRef,
            indexConfig,
            searchConfig,
            tableColumn,
            requestConfig,
            dialogConfig,
            iconVisible,
            iconNames,
            sibFormRef,
            formItemChange(data, field, form) {
                if (field.code == 'menuType') changeMenuTypeFields(form);
            },
            handleSelectIcon(icon, form) {
                form.icon = icon;
                iconVisible.value = false;
            },
            handleAdd(row) {
                Object.assign(dialogConfig, {
                    type: 'add',
                    title: row.menuId ? `从【${row.menuName}】新增子菜单` : '新增菜单',
                    form: {},
                    visible: true,
                });
                const rowMenuType = (row || {}).menuType;
                let sonMenuType = 'M';
                if (rowMenuType === 'M') sonMenuType = 'C';
                if (rowMenuType === 'C') sonMenuType = 'F';
                changeMenuTypeFields({ parentId: row.menuId || 0, menuType: sonMenuType });
            },
            handleCopy(row) {
                Object.assign(dialogConfig, {
                    type: 'copy',
                    title: `从【${row.menuName}】复制`,
                    visible: true,
                });
                const newRow = window.SIB.deepCopy(row);
                delete newRow.menuId;
                changeMenuTypeFields(newRow);
            },
            handleEdit(row) {
                dialogConfig.type = 'edit';
                // prettier-ignore
                axios.get(`/system/menu/${row.menuId}`).then((res) => {
                    dialogConfig.title = `编辑【${row.menuName}】`;
                    dialogConfig.visible = true;
                    changeMenuTypeFields(res.data || row || {});
                });
            },
            handleDelete(row) {
                // prettier-ignore
                ElMessageBox.confirm(`是否删除【${row.menuName}】`, '温馨提示').then(() => {
                    axios.delete(`/system/menu/${row.menuId}`).then(() => {
                        ElMessage({ type: 'success', message: '删除成功' });
                        sibTableRef.value.getTableData();
                    });
                }).catch(() => {});
            },
            handleSubmit(form, cb) {
                const params = window.SIB.deepCopy(form);
                if (Object.prototype.toString.call(params.parentId) === '[object Array]' && params.parentId.length) {
                    if (params.parentId[0].menuId || params.parentId[0].menuId === 0) params.parentId = params.parentId[0].menuId;
                    else params.parentId = params.parentId[0] || 0;
                }
                const method = dialogConfig.type === 'edit' ? 'put' : 'post';
                // prettier-ignore
                axios[method]('/system/menu', params).then(() => {
                    dialogConfig.visible = false;
                    dialogConfig.form = {};
                    sibTableRef.value.getTableData();
                }).finally(cb);
            },
        };
    },
};
</script>

<style lang="scss">
.system-menu {
    height: calc(100vh - 84px);

    .sib-table {
        height: 100%;

        .sib-form {
            /* stylelint-disable-next-line selector-class-pattern */
            .sib-form-item__operations .el-form-item__content {
                justify-content: flex-start !important;
            }
        }
    }
}

.form-dialog {
    .sib-form {
        .icon-input {
            height: var(--el-input-height);

            .el-input__wrapper {
                width: 100%;
                height: 100%;
            }
        }
    }
}

.menu-icon-list {
    display: flex;
    flex-wrap: wrap;

    .icon-box {
        width: calc(50% - 10px);
        margin: 5px 0;
        font-size: 16px;
        cursor: pointer;

        svg {
            margin-right: 5px;
        }

        &:nth-child(2n - 1) {
            margin-right: 20px;
        }
    }
}
</style>
